#pragma once

#include "MyBase.h"

namespace my
{
	class Ref
	{
	public:
		Ref() : m_ref_count(1) {}
		virtual ~Ref() {}

		Ref(const Ref &other) = delete;
		Ref(Ref &&other) = delete;
		Ref& operator =(const Ref &other) = delete;
		Ref& operator =(Ref &&other) = delete;

		void retain() noexcept
		{
			m_ref_count.fetch_add(1, std::memory_order_relaxed);
		}

		void release() noexcept
		{
			if (m_ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1)
			{
				delete this;
			}
		}

	private:
		std::atomic_int32_t m_ref_count;
	};


	class impl_bool_type;


	template <typename T>
	class Ptr
	{
	public:
		// T should be Ref, or derived from Ref
		// static_assert(std::is_same<Ref, T>::value || std::is_base_of<Ref, Ptr<T> >::value);

		Ptr(T* ptr) : m_ptr(ptr) { if (ptr) ptr->retain(); }
		~Ptr() { if (m_ptr) m_ptr->release(); }

		template <typename T2>
		Ptr(T2* ptr) : m_ptr(ptr) { if (ptr) ptr->retain(); }

		Ptr(const Ptr &other) : m_ptr(other.m_ptr) { if (m_ptr) m_ptr->retain(); }

		template <typename T2>
		Ptr(const Ptr<T2> &other) : m_ptr(other.get()) { if (m_ptr) m_ptr->retain(); }

		Ptr(Ptr &&other) noexcept : m_ptr(other.m_ptr) { other.m_ptr = nullptr; }

		template <typename T2>
		Ptr(Ptr<T2> &&other) noexcept : m_ptr(other.get()) { other = nullptr; }

		Ptr &operator=(const Ptr &other)
		{
			if (other.m_ptr) other.m_ptr->retain();
			if (m_ptr) m_ptr->release();
			m_ptr = other.m_ptr;

			return *this;
		}

		Ptr &operator=(Ptr &&other)
		{
			if (m_ptr) m_ptr->release();
			m_ptr = other.m_ptr;
			other.m_ptr = nullptr;

			return *this;
		}

		T *get() const
		{
			return m_ptr;
		}

		T &operator *() const
		{
			return *m_ptr;
		}

		T *operator ->() const
		{
			return m_ptr;
		}

		operator impl_bool_type *() const
		{
			return m_ptr ? reinterpret_cast<impl_bool_type*>(ptrdiff_t(128)) : nullptr;
		}

	private:
		T *m_ptr;
	};


	template <typename T, typename ... ARGS>
	Ptr<T> make_ptr(ARGS&&... args)
	{
		T *p = new T(std::forward<ARGS>(args)...);
		return Ptr<T>(p);
	}


	template <typename T1, typename T2>
	Ptr<T1> static_ptr_cast(const Ptr<T2> &t2)
	{
		T2 *p = t2.get();
		return Ptr<T1>(static_cast<T1 *>(p));
	}


	template <typename T1, typename T2>
	Ptr<T1> dynamic_ptr_cast(const Ptr<T2> &t2)
	{
		T2 *p = t2.get();
		return Ptr<T1>(dynamic_cast<T1 *>(p));
	}


	template <typename T>
	Ptr<T> const_ptr_cast(const Ptr<const T> &t2)
	{
		const T *p = t2.get();
		return Ptr<T>(const_cast<T *>(p));
	}


	template <typename T>
	class Singleton
	{
	public:
		Singleton() {}
		Singleton(const Singleton &other) = delete;
		Singleton(Singleton &&other) = delete;
		Singleton &operator =(const Singleton &other) = delete;
		Singleton &operator =(Singleton &&other) = delete;

		static T &instance()
		{
			static T s_instance;
			return s_instance;
		}
	};
}
